package jflowmap.model.ontology;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import jflowmap.model.ontology.interfaces.Named;
import jflowmap.util.InformativeComparator;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

public final class Dimension implements Comparable<Dimension>, Named {
	public enum GrammaticalCategory {
		MALE, FEMALE, NEUTRUM, MALE_PLURAL, FEMALE_PLURAL, NEUTRUM_PLURAL
	}

	public static final InformativeComparator<Dimension> COMPARE_BY_NAME = new InformativeComparator<Dimension>() {
		@Override
		public int compare(Dimension arg0, Dimension arg1) {
			return arg0.getName().compareTo(arg1.getName());
		}

		@Override
		public String getDescription() {
			return "alphabetisch geordnet";
		}
		
	};
	
	
	public static Set<Dimension> getAllAncestorsOf (Set<Dimension> dimensions) {
		Set<Dimension> tmp = new HashSet<Dimension>();
		
		for (Dimension dim: dimensions) {
			tmp.addAll(dim.getDeepParents());
		}
		
		return tmp;
	}

	public static Set<Dimension> getCommonAncestorsOf (Set<Dimension> dimensions) {
		Set<Dimension> tmp = new HashSet<Dimension>();
		Iterator<Dimension> iter = dimensions.iterator();

		if (iter.hasNext()) {
			final Dimension d = iter.next();
			tmp.addAll(d.getDeepParents());
			tmp.add(d);
		}

		while (iter.hasNext()) {
			final Dimension d = iter.next();
			Set<Dimension> other = new HashSet<Dimension>();
			other.add(d);
			other.addAll(d.getDeepParents());
			tmp = Sets.intersection(tmp, other);
		}

		final Set<Dimension> helper = tmp;

		tmp = Sets.newHashSet(Iterables.filter(tmp, new Predicate<Dimension>() {
			@Override
			public boolean apply(Dimension dim) {
				for (Dimension other: helper) {
					if (other.isDeepChildOf(dim)) {
						return false;
					}
				}
				return true;
			}

		}));

		return tmp;
	}

	public static Set<Dimension> getCommonDirectParentsOf(Dimension one, Dimension another) {
		Set<Dimension> tmp = new HashSet<Dimension>();
		for (Dimension oneParent: one.parents) {
			for (Dimension anotherParent: another.parents) {
				if (oneParent.equals(anotherParent))
					tmp.add(oneParent);
			}
		}
		return tmp;
	}

	private final Map<String, Object> attributes = new HashMap<String, Object>();
	
	private final Set<Dimension> parents = new HashSet<Dimension>();
	private final Set<Dimension> children = new HashSet<Dimension>();
	private final Set<Dimension> parts = new HashSet<Dimension>();
	private final Set<Dimension> ensembles = new HashSet<Dimension>();
	
	private final List<Value> values = new Vector<Value>();

	private final String name;
	private final Context context;

	private Dimension predecessor;

	private Dimension successor;
	Dimension(final String name, final Context context) {
		this.name = name;
		this.context = context;
	}
	
	public Collection<Value> getValues() {
		return Collections.unmodifiableList(values);
	}

	void addValue (Value value) {
		values.add(value);
	}
	public <T> boolean canGet (String attributeName, Class<T> attributeClass) {
		return attributes.get(attributeName) != null
				&& attributes.get(attributeName).getClass() == attributeClass;
	}

	public Dimension childOf(Dimension parent) {
		this.parents.add(parent);
		parent.children.add(this);
		return this;
	}

	public Dimension childOf (String parentName) {
		return childOf(context.getDimension(parentName));		
	}

	public Set<Dimension> children() {
		return Collections.unmodifiableSet(this.children);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Dimension other = (Dimension) obj;
		if (context == null) {
			if (other.context != null)
				return false;
		} else if (!context.equals(other.context))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	public Dimension following (String predecessorName) {
		Dimension predecessor = context.getDimension(predecessorName);
		this.predecessor = predecessor;
		predecessor.successor = this;
		return this;
	}

	public <T> T get (String attributeName, Class<T> attributeClass) {
		return attributeClass.cast(attributes.get(attributeName));
	}

	public Set<String> getAttributeKeys () {
		return attributes.keySet();
	}


	public Set<Dimension> getDeepParents() {
		Set<Dimension> tmp = new HashSet<Dimension>(parents);
		for (Dimension parent: parents) {
			tmp = Sets.union(tmp, parent.getDeepParents());
		}
		return tmp;
	}

	public final GrammaticalCategory getGrammaticalCategory() {
		return this.get("grammar", GrammaticalCategory.class);
	}

	@Override
	public String getName() {
		return this.name;
	}

	public Dimension getPredecessor() {
		return predecessor;
	}

	public Dimension getSuccessor() {
		return successor;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((context == null) ? 0 : context.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}


	public boolean isDeepChildOf (final Dimension parent) {
		if (this.isDirectChildOf(parent)) {
			return true;
		}

		for (Dimension mother: parents) {
			if (mother.isDeepChildOf(parent)) {
				return true;
			}
		}
		return false;
	}	

	public boolean isDeepPartOf (final Dimension ensemble) {
		if (this.isDirectPartOf(ensemble)) {
			return true;
		}

		for (Dimension greaterPart: ensembles) {
			if (greaterPart.isDeepPartOf(ensemble)) {
				return true;
			}
		}

		return false;
	}

	public boolean isDirectChildOf (final Dimension parent) {
		if (this.parents.contains(parent)) {
			return true;
		}
		return false;
	}

	public boolean isDirectPartOf (final Dimension ensemble) {
		if (this.ensembles.contains(ensemble)) {
			return true;
		}
		return false;
	}

	public boolean isFirst() {
		return predecessor == null;
	}

	public boolean isLast() {
		return successor == null;
	}

	public Dimension parentOf (String childName) {
		final Dimension child = context.getDimension(childName);
		this.children.add(child);
		child.parents.add(this);
		return this;
	}

	public Dimension partOf (String ensembleName) {
		final Dimension ensemble = context.getDimension(ensembleName);
		ensemble.parts.add(this);
		this.ensembles.add(ensemble);
		return this;
	}

	public Set<Dimension> parts() {
		return Collections.unmodifiableSet(this.children);
	}

	public Dimension preceding (String successorName) {
		final Dimension successor = context.getDimension(successorName);
		this.successor = successor;
		successor.predecessor = this;		
		return this;
	}

	public <T> Dimension set (String attributeName, T attributeValue) {
		if (attributeName.equals("grammar")) {
			if (attributeValue instanceof String)
				attributes.put(attributeName, GrammaticalCategory.valueOf((String) attributeValue));
			else
				attributes.put(attributeName, (GrammaticalCategory)attributeValue);
			return this;
			
		} else if (attributeValue instanceof String) {
			try {
				Double doubleValue = Double.parseDouble((String) attributeValue);
				attributes.put(attributeName, doubleValue);
				return this;

			} catch (NumberFormatException ex) { /*ignore*/ }
			
		}
		
		attributes.put(attributeName, attributeValue);
		return this;
	}

	@Override
	public String toString() {
		return "Dimension [name=" + name + "]";
	}

	public boolean isFinal() {
		return children.size() == 0 && parts.size() == 0;
	}

	public Set<Dimension> parents() {
		return Collections.unmodifiableSet(this.parents);
	}

	public static Set<Dimension> getSiblings(Dimension dimension) {
		Set<Dimension> result = Sets.newHashSet(dimension);
		
		for (Dimension parent: dimension.parents) {
			result = Sets.union(result, parent.children);
		}
		
		System.out.println("Siblings of " + dimension + ": " + result);
		System.out.flush();
		return result;
	}

	@Override
	public int compareTo(Dimension other) {
		return this.getName().compareTo(other.getName());
	}
}
